﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Glissando
{
    public enum Chord
    {
        SingleNote, MinorSecond, MajorSecond, MinorThird, MajorThird, PerfectFourth, DiminishedFifth, PerfectFifth,
        MinorSixth, MajorSixth, MinorSeventh, MajorSeventh, PerfectOctave,
        MajorTriad, MinorTriad, AugmentedTriad, DiminishedTriad,
        DiminishedSeventh, MinorSeventhChord
    }

    public enum Scale
    {
        C, Csharp, D, Dsharp, E, F, Fsharp, G, Gsharp, A, Asharp, B
    };

    public enum ScaleModifier
    {
        Major, Minor, Blues
    }

    // http://en.wikipedia.org/wiki/Pitch_(music)
    // http://en.wikipedia.org/wiki/Pitch_class
    // http://en.wikipedia.org/wiki/Note_(music)
    // http://en.wikipedia.org/wiki/Musical_scale
    // http://en.wikipedia.org/wiki/Chord_(music)
    // http://en.wikipedia.org/wiki/Perfect_interval
    // http://en.wikipedia.org/wiki/Perfect_fifth

    // todo - create viewmodel

    public class PitchTest
    {
        private Random randomizer = new Random();

        public int Count { get; set; }
        public int CorrectCount { get; set; }
        public int IncorrectCount { get; set; }

        // after the user has pressed the correct key, this flag is true - cannot press more times until next test
        public bool CorrectChordPressed { get; set; }

        public List<Chord> TestChords { get; private set; }
        public List<Scale> TestScales { get; private set; }
        public List<ScaleModifier> TestScaleModifiers { get; private set; }

        public int MinOctave { get; set; }
        public int MaxOctave { get; set; }

        public int[] TestChord { get; private set; }
        public string TestChordName { get; set; }
        public List<int> Pressed = new List<int>();

        public PitchTest()
        {
            TestChord = null;
            TestChords = new List<Chord>();
            TestScales = new List<Scale>();
            TestScaleModifiers = new List<ScaleModifier>();

            TestChords.Add(Chord.SingleNote);
            /*  TestChords.Add(Chord.PerfectOctave);
              TestChords.Add(Chord.PerfectFifth);
              TestChords.Add(Chord.PerfectFourth);*/
            TestChords.Add(Chord.MajorTriad);
            TestChords.Add(Chord.MinorTriad);

            TestScales.Add(Scale.C);
            //TestScales.Add(Scale.F);
            //TestScales.Add(Scale.G);
            //TestScales.Add(Scale.Asharp);

            MinOctave = 4;
            MaxOctave = 4;
        }

        public void Start()
        {
            CorrectCount = 0;
            IncorrectCount = 0;
            Count = 0;
        }

        public string GetScaleName(Scale s, bool flat, ScaleModifier modifier)
        {
            string name = GetScaleName(s, flat);
            if (modifier == ScaleModifier.Minor) name = name.ToLower();
            return name;
        }

        public string GetScaleName(Scale s, bool flat)
        {
            switch (s)
            {
                case Scale.C:
                case Scale.D:
                case Scale.E:
                case Scale.F:
                case Scale.G:
                case Scale.A:
                case Scale.B: return s.ToString();
            }
            if (flat)
            {
                switch (s)
                {
                    case Scale.Csharp: return "Db";
                    case Scale.Dsharp: return "Eb";
                    case Scale.Fsharp: return "Gb";
                    case Scale.Gsharp: return "Ab";
                    case Scale.Asharp: return "Bb";
                }
            }
            else
            {
                switch (s)
                {
                    case Scale.Csharp: return "C#";
                    case Scale.Dsharp: return "D#";
                    case Scale.Fsharp: return "F#";
                    case Scale.Gsharp: return "G#";
                    case Scale.Asharp: return "A#";
                }
            }
            return null;
        }

        public string GetChordName(Chord c, Scale s)
        {
            string major = GetScaleName(s, false);
            string minor = GetScaleName(s, false, ScaleModifier.Minor);

            switch (c)
            {
                case Chord.SingleNote: return major;
                case Chord.MinorSecond: return minor + "m2";
                case Chord.MajorSecond: return major + "M2";
                case Chord.MinorThird: return minor + "m3";
                case Chord.MajorThird: return minor + "M3";
                case Chord.PerfectFourth: return major + "P4";
                case Chord.DiminishedFifth: return major + "TT";
                case Chord.PerfectFifth: return major + "P5";
                case Chord.MinorSixth: return minor + "m6";
                case Chord.MajorSixth: return major + "M6";
                case Chord.MinorSeventh: return minor + "m7";
                case Chord.MajorSeventh: return major + "M7";
                case Chord.PerfectOctave: return major + "P8";
                case Chord.MajorTriad: return major + " (triad)";
                case Chord.MinorTriad: return minor + " (triad)";
                case Chord.AugmentedTriad: return major + "aug (triad)";
                case Chord.DiminishedTriad: return major + "dim (triad)";
                case Chord.DiminishedSeventh: return major + "dim7 (seventh)";
                case Chord.MinorSeventhChord: return major + "m7 (seventh)";
            }
            return "";
        }

        public int[] GetChord(Chord c, Scale s, int octave)
        {
            int n0 = (octave + 1) * 12 + (int)s;
            switch (c)
            {
                case Chord.SingleNote: return new[] { n0 };
                case Chord.MinorSecond: return new[] { n0, n0 + 1 };
                case Chord.MajorSecond: return new[] { n0, n0 + 2 };
                case Chord.MinorThird: return new[] { n0, n0 + 3 };
                case Chord.MajorThird: return new[] { n0, n0 + 4 };
                case Chord.PerfectFourth: return new[] { n0, n0 + 5 };
                case Chord.DiminishedFifth: return new[] { n0, n0 + 6 };
                case Chord.PerfectFifth: return new[] { n0, n0 + 7 };
                case Chord.MinorSixth: return new[] { n0, n0 + 8 };
                case Chord.MajorSixth: return new[] { n0, n0 + 9 };
                case Chord.MinorSeventh: return new[] { n0, n0 + 10 };
                case Chord.MajorSeventh: return new[] { n0, n0 + 11 };
                case Chord.PerfectOctave: return new[] { n0, n0 + 12 };
                case Chord.MajorTriad: return new[] { n0, n0 + 4, n0 + 7 };
                case Chord.MinorTriad: return new[] { n0, n0 + 3, n0 + 7 };
                case Chord.AugmentedTriad: return new[] { n0, n0 + 4, n0 + 8 };
                case Chord.DiminishedTriad: return new[] { n0, n0 + 3, n0 + 6 };
                case Chord.DiminishedSeventh: return new[] { n0, n0 + 3, n0 + 6, n0 + 9 };
                case Chord.MinorSeventhChord: return new[] { n0, n0 + 3, n0 + 7, n0 + 10 };
            }
            return null;
        }

        public void FindNewTestChord()
        {
            int it = 0;
            while (it < 10)
            {
                it++;
                
                Chord c = Chord.SingleNote;
                if (TestChords.Count > 0)
                    c = TestChords[randomizer.Next(TestChords.Count)];
                Scale s = Scale.C;
                if (TestScales.Count > 0)
                    s=TestScales[randomizer.Next(TestScales.Count)];
                ScaleModifier m = ScaleModifier.Major;
                if (TestScaleModifiers.Count > 0)
                    m = TestScaleModifiers[randomizer.Next(TestScaleModifiers.Count)];
                int o = MinOctave + randomizer.Next(MaxOctave - MinOctave);

                int[] NewTestChord;
                if (c == Chord.SingleNote)
                {
                    NewTestChord = GetSingleNote(s, m, o);
                    TestChordName = GetSingleNoteName(NewTestChord[0]);
                }
                else
                {
                    NewTestChord = GetChord(c, s, o);
                    TestChordName = GetChordName(c, s);
                }
                if (!AreChordsEqual(NewTestChord, TestChord))
                {
                    TestChord = NewTestChord;
                    break;
                }
            }
            Pressed.Clear();

            Count++;
            CorrectChordPressed = false;
        }

        private bool AreChordsEqual(int[] a, int[] b)
        {
            if (a == null || b == null)
                return false;
            if (a.Length != b.Length)
                return false;
            for (int i = 0; i < a.Length; i++)
                if (a[i] != b[i]) return false;
            return true;
        }

        private string GetSingleNoteName(int p)
        {
            int n2 = p % 12;
            int o = p / 12 - 1;
            string[] notes = new string[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
            return String.Format("{0}{1}", notes[n2], o);
        }

        private int[] GetSingleNote(Scale s, ScaleModifier sm, int o)
        {
            int[] scale = null;
            if (sm == ScaleModifier.Major)
                scale = new int[] { 0, 2, 4, 5, 7, 9, 11 };
            if (sm == ScaleModifier.Minor)
                scale = new int[] { 0, 2, 3, 5, 7, 9, 11 };
            if (sm == ScaleModifier.Blues)
                scale = new int[] { 0, 3, 5, 6, 7, 10 };

            int n0 = (o + 1) * 12 + (int)s;
            int n1 = scale[randomizer.Next(scale.Length)];
            return new int[] { n0 + n1 };
        }

        public string TestResult
        {
            get { return String.Format("{0} : {1} / {2}", CorrectCount, IncorrectCount, Count); }
        }

        public void PressKey(int note)
        {

            if (!Pressed.Contains(note))
                Pressed.Add(note);
        }

        public void ReleaseKey(int note, bool dontForget)
        {
            if (dontForget)
            {
                if (TestChord != null && Pressed.Count >= TestChord.Length)
                    Pressed.Clear();
            }
            else
            {
                if (Pressed.Contains(note))
                    Pressed.Remove(note);
            }
        }

        public bool Check()
        {
            if (CorrectChordPressed)
                return false;
            if (TestChord == null)
                return false;

            if (Pressed.Count != TestChord.Length)
                return false;

            bool correct = true;
            foreach (int note in TestChord)
                if (!Pressed.Contains(note))
                    correct = false;

            if (correct)
            {
                CorrectCount++;
                CorrectChordPressed = true;
                return true;
            }
            else
            {
                IncorrectCount++;
                return false;
            }
        }

    }
}
